#ifndef __GLF_APPLICATION_H_INCLUDED__
#define __GLF_APPLICATION_H_INCLUDED__

#include <glf/core/inlines.h>

#include <glf/appEvent.h>
#include <glf/input/inputManager.h>
#include <glf/io/fs.h>
#include <glf/core/mutex.h>

// Shortcuts
#define GetAppEventMgr()	glf::App::GetInstance()->GetEventMgr()
#define GetAppInputMgr()	glf::App::GetInstance()->GetInputMgr()

#ifndef GLF_CREATIONPARAMS_APPNAME_SIZE
	#define GLF_CREATIONPARAMS_APPNAME_SIZE 64
#endif

#ifndef GLF_MAX_EXTRA_CONTEXT
	#define GLF_MAX_EXTRA_CONTEXT 1 // rarely will we need more than one extra, so this is a very reasonable default value
#endif

namespace glf {

	namespace remote
	{
		class Controller;
	} //eon remote

// Note : union and structure are now names as anonymous union/struct cause
//        problems with RVCT
struct PlatformInfo {
	void* hWindow;						// HWND, X11 Window, KDWindow, etc...
	void* hSecondWindow;				// HWND, X11 Window, KDWindow, etc...
	union {
		struct{ // SDL
			void* sdlScreen;
			int sdlFlags;				//HINSTANCE
		} mSDL;
		struct{ // Windows
			void* hInstance;			//HINSTANCE
			void* hRc;					//HGLRC
			void* hDc;					//HDC
			void* hSecondDc;			//HDC
		} mWindows;
		struct { // MacOSX
			void* cglContext;
			void* cglPixelFormat;
		} mMacOSX;
		struct{ // EGL/OpenKD
			void* kdWindow;				//KDWindow
			void* eglDisplay;			//EGLDisplay
			void* eglWindowSurface;		//EGLSurface
			void* eglContext;	        //EGLContext
			void* eglConfig;			//EGLConfig
			const void* eglConfigAttribs;
			const void* eglSurfaceAttribs;
			const void* eglContextAttribs;
		} mEGL;
		struct{ // X11 (Linux/Unix)
			void* xDisplay;				//Display
			void* glxDrawable;			//GLXDrawable
			void* glxWindow;			//GLXWindow glxWin;
			void* glxContext;			//GLXContext Context;
		} mX11;
		struct{ // PS3
			void* psglDevice;			// PSGL Context
			void* psglContext;			// PSGL Device
		} mPS3;
		struct{ // PSP2
			void* gxmContext;			// gxm Device
		} mPSP2;
	} mInfo;

	// The order data in mExtraContext might change over time; always pass via
	// App's API to use extra contexts.
	void* mExtraContext[GLF_MAX_EXTRA_CONTEXT];
};
	

enum Region {
	REGION_NA,
	REGION_EU,
	REGION_JP,
	REGION_XX,
	REGION_COUNT
};
//
//enum Lang {
//	LANG_ENGLISH,
//	LANG_FRENCH,
//	LANG_GERMAN,
//	LANG_ITALIAN,
//	LANG_SPANISH,
//	LANG_DUTCH,
//	LANG_JAPANESE,
//	//insert more here
//	LANG_COUNT,
//	
//	LANG_EN = LANG_ENGLISH,
//	LANG_FR, LANG_GE, LANG_IT, LANG_SP, LANG_DU, LANG_JP,
//};

struct LangInfo {
	const char* name;
	const char* shortName;
};

//const LangInfo& GetLangInfo(Lang lang);

struct AppSettings : private NonCopyable {
	Region region;
	//Lang lang;
	float aspect; // w / h
	bool isProgressive;
	bool isMultitaskSupported;
	
	float screenScale;
	size_t screenWidth;
	size_t screenHeight;
};

enum FullScreenAntiAliasSetting
{
	AA_NONE = 0,
	AA_2X,
	AA_4X,
	AA_4XR,
	AA_8X,
	AA_16X,

};

enum BufferingMode
{
	BUFFER_MODE_DOUBLE = 0,
	BUFFER_MODE_TRIPLE
};

enum UpdateMode
{
	UPDATE_MODE_NONE,
	UPDATE_MODE_DISPLAY_SYNC,
	UPDATE_MODE_TIMER,
	UPDATE_MODE_THREAD,
	UPDATE_MODE_EVENTPUMP
};

enum RendererType
{	
	NULL_RENDER  = 0x0000,

	OPENGL_ES_1  = 0x0001,
	OPENGL_ES_2  = 0x0002,
	OPENGL_ES    = OPENGL_ES_1|OPENGL_ES_2, // For any GLES driver

	OPENGL_FIXED = 0x0004,
	OPENGL_GLSL  = 0x0008,
	OPENGL_CG    = 0x0010,
	OPENGL		 = OPENGL_FIXED|OPENGL_GLSL|OPENGL_CG, // For any GL Desktop

	OPENGL_ANY   = OPENGL | OPENGL_ES,

	DIRECT3D_11  = 0x0020,

    GXM_PSP2     = 0x0040,

	PLATFORM_SPECIFIC = 0x0080   // For others, like Wii, PSP, etc
};

enum Orientation {
	ORIENTATION_UNDEFINED			= 0,
	ORIENTATION_PORTRAIT			= 1<<0,
	ORIENTATION_PORTRAIT_REVERSED	= 1<<1,
	ORIENTATION_LANDSCAPE_RIGHT		= 1<<2,
	ORIENTATION_LANDSCAPE_LEFT		= 1<<3,
	ORIENTATION_ALL					=	ORIENTATION_PORTRAIT | 
										ORIENTATION_PORTRAIT_REVERSED |
										ORIENTATION_LANDSCAPE_RIGHT |
										ORIENTATION_LANDSCAPE_LEFT
};

enum DisplayStatus {
	DS_UNAVAILABLE,
	DS_AVAILABLE,
	DS_CONNECTED
};

struct CreationSettings {
	static const int DEFAULT_VALUE = 0x12345678;

	CreationSettings() 
		: windowPosX(DEFAULT_VALUE) 
		, windowPosY(DEFAULT_VALUE) 
		, windowWidth(DEFAULT_VALUE) 
		, windowHeight(DEFAULT_VALUE) 
		, createWindow(false)
		, waitVSync(false) 
		, useInterlace(false)
		, useStencilbuffer(false)
		, isFullScreen(false)
		, hiddenFrame(false)
		, hiddenWindow(false)
		, updateMode(UPDATE_MODE_DISPLAY_SYNC) 
        , enableStereoDisplay(true)
		, startFtpServer(false)
		, enableDebugger(true)
		, deviceName(0)
		, screenScale(1.0f)
		, driverType(OPENGL_FIXED) 		
		, pixelSize(16) 
		, zBufferSize(16) 
		, antiAliasSetting(AA_NONE) 
		, bufferingMode(BUFFER_MODE_DOUBLE)
		// swapBuffersOnPresent : automatic swap buffers when App::Present is called.
		// Useful for glf only applications (like the remote controller)
		, swapBuffersOnPresent(false)
		, extraContexts(0)
		, useNonLinearDepth(true)
		, defaultOrientation(ORIENTATION_UNDEFINED)
		, supportedOrientations(ORIENTATION_UNDEFINED)
	{
		Strcpy(appName, "appname");
	}

	int windowPosX;
	int windowPosY;
	int windowWidth;
	int windowHeight;
	bool createWindow;
	bool waitVSync;
	bool useInterlace;
	bool useStencilbuffer;
	bool isFullScreen;
	bool hiddenFrame;
	bool hiddenWindow;

	UpdateMode updateMode;
    bool enableStereoDisplay;
	
	bool startFtpServer; //port 7930
	
	bool enableDebugger;			// default: true (used only when GLF_ENABLE_DEBUGGER is active)

	const char* deviceName;
	float screenScale;

	RendererType driverType;	// default : OPENGL_FIXED ?
	unsigned char pixelSize;	// default : 16 (bpp)
	unsigned char zBufferSize;	// default : 16
	FullScreenAntiAliasSetting antiAliasSetting;// default : AA_NONE
	BufferingMode bufferingMode;// default : BUFFER_MODE_DOUBLE
	bool swapBuffersOnPresent;

	int extraContexts;

	bool useNonLinearDepth;

	Orientation defaultOrientation;
	int supportedOrientations;

	char appName[GLF_CREATIONPARAMS_APPNAME_SIZE];
	char params[8][64];
	size_t numParams;
};



class App : 
	public EventReceiver,
	private NonCopyable {
	
public:
	enum CreationFlag {
		CREATIONFLAG_NONE							= 0x0000,
		CREATIONFLAG_WIN32_DONT_CHANGE_CWD			= 0x0001,
		CREATIONFLAG_WIN32_DONT_CHANGE_APPDATA		= 0x0002,
		CREATIONFLAG_WIN32_DONT_CHANGE_TMP			= 0x0004,
	};

	virtual void SetCreationSettings(CreationSettings& cs);
	virtual void SetPlatformInfos(PlatformInfo& info);

	void SetAsMainThread();
	void UnsetAsMainThread();
	
	virtual bool Init(CreationSettings& cs);	
	virtual void Deinit();
	virtual void Run();
	virtual void Destroy();
	virtual void Update();
	virtual bool QueryInfo(const char* key, char* result, size_t& resultLength);
	virtual void BeginProfilerEvent(const char* name);
	virtual void EndProfilerEvent();
	void EnableProfilerEvents(bool);
	bool IsProfilerEventsEnabled() const;
	
	//! Get the raw framebuffer object for the specified window.
	void *GetFrameBufferObject(int windowIndex = 0);
	//! Returns true is there is a valid framebuffer object for the specified window.
	bool HasFrameBufferObject(int windowIndex = 0) const;
	//! Prepare the graphic context before any draw for the specified window.
	//! Returns true if the context is valid.
	bool Prepare(int windowIndex = 0);
	//! Flush/swap/present all pending draws for the specified window.
	void Present(int windowIndex = 0);

	//! Connect display specified by windowIndex. 
	//! Precondition: GetDisplayStatus(windowIndex) == DS_AVAILABLE.
	//! Returns true if the display was connected successfully.
	bool ConnectDisplay(int windowIndex);
	//! Disconnect display specified by windowIndex. 
	//! Precondition: GetDisplayStatus(windowIndex) == DS_CONNECTED.
	//! Returns true if the display was disconnected successfully.
	bool DisconnectDisplay(int windowIndex);
	//! Get display status for the specified window. 
	DisplayStatus GetDisplayStatus(int windowIndex);
	
	bool IsRunnable() const;
	bool IsRunning() const;
	// Force the default Run() implementation to stop
	void Stop();
	
	void SetTargetFps(float fps);
	float GetTargetFps() const;

	PlatformInfo& GetPlatformInfos() ;
	const AppSettings& GetAppSettings() const;
	const CreationSettings& GetCreationSettings() const;

	bool HasContext() const;
	bool ReserveContext();
	bool ReleaseContext();
	
	Orientation GetOrientation() const;
	bool IsOrientationSupported(Orientation orientation) const;
	bool SetOrientation(Orientation newOrientation);	
	void SetSupportedOrientations(int supportedOrientations);

	// Kinds of coordinates used by conversion utilities:
	// - device coordinates: raw coordinates coming from OS and independent from rotation and scale
	// - screen coordinates: coordinates adjusted according to rotation and scale
	// - normalized device coordinates: same coordinates as device but normalized between 0..1
	// - normalized screen coordinates: same coordinates as screen but normalized between 0..1

	// device coordinates <-> screen coordinates
	Point<short> ConvertPosDeviceToScreen(const Point<float> &devicePos) const;	
	Point<float> ConvertPosScreenToDevice(const Point<short> &screenPos) const;
	
	// screen coordinates <-> normalized device coordinates
	Point<float> ConvertPosScreenToNormalizedDevice(const Point<short> &screenPos) const;	
	Point<short> ConvertPosNormalizedDeviceToScreen(const Point<float> &normalizedDevicePos) const;
	
	// screen coordinates <-> normalized screen coordinates
	Point<float> ConvertPosScreenToNormalizedScreen(const Point<short> &screenPos) const;	
	Point<short> ConvertPosNormalizedScreenToScreen(const Point<float> &normalizedScreen) const;

	static App *GetInstance();

	//get mgr
	EventManager& GetEventMgr();
	InputManager& GetInputMgr();
	Fs& GetFs();
	remote::Controller *GetRemoteController() { return mRemoteController; }
	void SetRemoteController(remote::Controller *remoteController);

	const char* GetDeviceName() const;

	// Window size adjusted according to rotation
	void GetScreenSize(int& width, int& height, int windowIndex = 0) const;
	
	// "True" window size
	void GetWindowSize(int& width, int& height, int windowIndex = 0) const;

#if GLF_VIRTUAL_FILESTREAM
	//
	void FileOutAllFiles();
#endif

	void StartRemoteController(const std::string& ip, int port=GLF_REMOTE_CONTROLLER_PORT);
		
protected:
	App(CreationFlag cf = CREATIONFLAG_NONE);
	virtual ~App();
	
	bool mIsRunning;
	
	EventManager& mEventManager;
	InputManager& mInputManager;
	Fs& mFs;
#if GLF_AS_DLL
public:
	static void SetInstance(App* pApp);
#endif //GLF_AS_DLL

private:
	
	Orientation mOrientation;
	int mSupportedOrientations;

	AppSettings mAppSettings;
	PlatformInfo mPlatformInfos;
	int mReservedExtraContexts;
	SpinLock mExtraContextLock;
	CreationSettings mCreationSettings;
	float mTargetFps;	
	static App *mInstance;
	remote::Controller *mRemoteController;
	
public:
	bool MyInit();

	GLF_DECLARE_IMPL(2500) //must be last
};

void init();
void Init();

struct ScopedReserveContext
{
	ScopedReserveContext()
	{
		// prevent recursively reserving context
		if(!App::GetInstance()->HasContext())
		{
			m_hasContext = App::GetInstance()->ReserveContext();
		}
		else
		{
			m_hasContext = false;
		}
	}

	~ScopedReserveContext()
	{
		if(m_hasContext)
		{
			App::GetInstance()->ReleaseContext();
		}
	}

private:

	bool m_hasContext;
};

} // end namespace GLF

#if GLF_AS_DLL
typedef glf::App* (*T_FUNC_NEWAPP)(int argc, char** argv);

extern T_FUNC_NEWAPP NewApp;

extern "C"
{
	void SetNewApp(T_FUNC_NEWAPP pNewApp);
	void SetGlfAlloc(T_FUNC_GLFALLOC pGlfAlloc);
	void SetGlfAlloc2(T_FUNC_GLFALLOC2 pGlfAlloc);
	void SetGlfFree(T_FUNC_GLFFREE pGlfFree);
};
#else
glf::App* NewApp(int argc, char** argv);
#endif // GLF_AS_DLL

#endif // __GLF_APPLICATION_H_INCLUDED__
